package owg.util.editor2d.operations;

import java.util.Arrays;
import java.util.HashMap;

import owg.util.data.KryoInstance.K;
import owg.util.data.property.Property;
import owg.util.data.property.PropertyUtil;
import owg.util.editor2d.Editor2D;
import owg.util.editor2d.entity.Placeable2D;
import owg.util.editor2d.entity.Rotateable2D;
import owg.util.editor2d.entity.Scaleable2D;

public class Transformation extends Operation
{
	public static class TransformationData extends Operation
	{
		public final int affectedIndex;
		public final double x, y;
		public final Float xScale,yScale;
		public final Float angle;
		public final HashMap<String, Object> parameterValues;

		@K private TransformationData(){ affectedIndex = 0; x = 0; y = 0; xScale = null; yScale = null; angle = null; parameterValues = null;}
		
		public TransformationData(Integer integer, Placeable2D p, String pName, Object pVal)
		{
			affectedIndex = integer;
			x = p.getX();
			y = p.getY();
			if(p instanceof Scaleable2D)
			{
				xScale = ((Scaleable2D) p).getXScale();
				yScale = ((Scaleable2D) p).getYScale();
			}
			else
			{
				xScale = null;
				yScale = null;
			}
			if(p instanceof Rotateable2D)
				angle = ((Rotateable2D) p).getAngle();
			else
				angle = null;
			parameterValues = new HashMap<>();
			for(Property<?> prop : PropertyUtil.getProperties(p))
			{
				parameterValues.put(PropertyUtil.nameOf(prop, p), prop.get());
			}
			if(pName != null)
			{
				if(parameterValues.containsKey(pName))
					parameterValues.put(pName, pVal);
				else
					System.err.println("Warning: "+p+" does not have a property with name "+pName);
			}
		}
		
		public void apply(Editor2D w)
		{
			Placeable2D p = w.worldObjectsImmutable.get(affectedIndex).a;
			
			Property<?>[] props = PropertyUtil.getProperties(p);
			if(props.length != parameterValues.size())
			{
				System.err.println("Warning: "+p+" has "+props.length+" properties, but "+this+" has "+parameterValues.size());
				System.err.println("p2d="+Arrays.toString(props));
				System.err.println("dat="+parameterValues.toString());
			}
			for(Property<?> prop : props)
			{
				String name = PropertyUtil.nameOf(prop, p);
				Object val = parameterValues.get(name);
				if(val == null)
					System.err.println("Warning: "+p+" has a property with name "+name+", but "+this+" does not!");
				else
				{
					try
					{
						set(prop, val);
					}
					catch(ClassCastException e)
					{
						System.err.println("Warning: "+val+" cannot be cast to "+prop.getType()+" at field "+p+"."+prop);
					}
				}
			}
			
			if(xScale != null)
			{
				assert yScale != null;
				if(p instanceof Scaleable2D)
					((Scaleable2D) p).setScale(xScale, yScale);
				else
					System.err.println("Warning: "+this+" has scaling data, but "+p+" is not scaleable...");
			}
			if(angle != null)
			{
				if(p instanceof Rotateable2D)
					((Rotateable2D) p).setAngle(angle);
				else
					System.err.println("Warning: "+this+" has rotation data, but "+p+" is not rotateable...");
			}
			p.setLocation(x, y);
		}
		
		private<X> void set(Property<X> prop, Object val) throws ClassCastException
		{
			prop.set(prop.cast(val));
		}
	}
	private TransformationData[] affections;
	@K private Transformation(){}
	/**
	 * @param selected The indices in the world objects which will be affected. Must not be null, and must not contain null.
	 * These must be indices and not references because objects may be destroyed and reconstructed.
	 */
	public Transformation(Editor2D e, Integer[] selected)
	{
		this(e, selected, null, null);
	}
	
	public Transformation(Editor2D e, Integer[] selected, String pName, Object pVal)
	{
		affections = new TransformationData[selected.length];
		for(int i = 0; i<selected.length; i++)
		{
			affections[i] = new TransformationData(selected[i], e.worldObjectsImmutable.get(selected[i]).a, pName, pVal);
		}
	}
	public TransformationData getData(int atIndex)
	{
		return affections[atIndex];
	}
	
	@Override
	public void apply(Editor2D w) throws OperationFailedException
	{
		for(TransformationData data : affections)
		{
			data.apply(w);
		}
	}
}